package com.zhuangxiaoyan.springmvc.dispachtServlet;

import com.zhuangxiaoyan.springmvc.annotation.*;
import com.zhuangxiaoyan.springmvc.spring.BeanDefinition;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Classname MyDispatchServlet
 * @Description TODO
 * @Date 2022/5/5 23:28
 * @Created by xjl
 */
public class MyDispatchServlet extends HttpServlet {

    private static final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>();
    private static final Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
    private static final Map<String, Object> mvc = new ConcurrentHashMap<String, Object>();
    private static final Map<String, Object> controllerMap = new ConcurrentHashMap<String, Object>();
    private static final Map<String, Method> handlerMapping = new ConcurrentHashMap<String, Method>();
    private final Properties properties = new Properties();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String requestURI = req.getRequestURI();
        System.out.println(requestURI);
        String contextPath = req.getContextPath();
        System.out.println(contextPath);
        requestURI.replace(contextPath, "").replace("/+", "/");
        if (!handlerMapping.containsKey(requestURI)) {
            resp.getWriter().write("404 NOT FOUND!");
            return;
        }
        Method method = handlerMapping.get(requestURI);
        //获取方法的参数列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        //获取注解
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        //获取请求参数列表
        Map<String, String[]> parameterMap = req.getParameterMap();
        //保存参数值
        Object[] paramValues = new Object[parameterTypes.length];
        for (int i = 0; i < parameterTypes.length; i++) {
            //根据参数名称，做某些处理
            String requestParam = parameterTypes[i].getSimpleName();

            if (requestParam.equals("HttpServletRequest")) {
                //参数类型已明确，这边强转类型
                paramValues[i] = req;
                continue;
            }
            if (requestParam.equals("HttpServletResponse")) {
                paramValues[i] = resp;
                continue;
            }
            //参数匹配填充
            if (requestParam.equals("String")) {
                String value = "";
                String[] param = null;
                boolean flag = true;
                for (int j = 0; j < parameterAnnotations[i].length; j++) {
                    if (parameterAnnotations[i][j] instanceof MyRequestParam) {
                        flag = false;
                        value = ((MyRequestParam) parameterAnnotations[i][j]).value();
                        boolean required = ((MyRequestParam) parameterAnnotations[i][j]).required();
                        param = parameterMap.get(value);
                        if (required && (null == param || param.length == 0)) {
                            resp.getWriter().write("Parameter error!");
                            return;
                        }
                    }
                    //未使用RequestParam的参数，jdk并未提供反射机制获取参数名，需要asm框架读取字节码文件,来获取方法的参数名称，此处简化不处理
                    if (flag) {

                    }
                }
                paramValues[i] = Arrays.toString(param).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
            }
        }
        try {
            method.invoke(controllerMap.get(requestURI), paramValues);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        System.out.println(config.getInitParameter("contextConfigLocation"));
        //加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //扫描设置配置的包
        String scanPackage = properties.getProperty("scanPackage");
        System.out.println(scanPackage);
        doScanner(scanPackage);
        //初始化
        doInstance();
        //初始化HandlerMapping(将url和method对应上)
        initHandlerMapping();
    }

    private void doLoadConfig(String location) {
        //把web.xml中的contextConfigLocation对应value值的文件加载到流里面
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
        //使用properties加载流文件
        try {
            properties.load(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != resourceAsStream) {
                try {
                    resourceAsStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void doScanner(String scanPackage) {
        URL resource = this.getClass().getClassLoader().getResource(scanPackage.replace(".", "/"));
        File file = new File(resource.getFile());
        for (File dir : file.listFiles()) {
            String className = scanPackage + "." + dir.getName().replace(".class", "");
            if (dir.isDirectory()) {
                doScanner(className);
            } else {
                try {
                    String beanName = dir.getName();
                    BeanDefinition beanDefinition = new BeanDefinition();
                    beanDefinition.setBeanName(dir.getName().substring(0, 1).toLowerCase() + beanName.substring(1).replace(".class", ""));
                    beanDefinition.setType("");
                    beanDefinition.setaClass(Class.forName(className));
                    beanDefinitionMap.put(dir.getName().replace(".class", ""), beanDefinition);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }

    }

    private void doInstance() {
        try {
            for (BeanDefinition beanDefinition : beanDefinitionMap.values()) {
                Class aClass = beanDefinition.getaClass();
                if (aClass.isAnnotationPresent(MyController.class)) {
                    createMVCInstance(aClass, beanDefinition.getBeanName());
                } else if (aClass.isAnnotationPresent(MyService.class)) {
                    createIOCInstance(aClass, beanDefinition.getBeanName());
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }

    private void createMVCInstance(Class aClass, String beanName) throws IllegalAccessException, InstantiationException {
        Object o = aClass.newInstance();
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            //依赖注入
            if (field.isAnnotationPresent(MyAutowired.class)) {
                String value = field.getName();
                Object o1 = ioc.get(value);
                if (o1 == null) {
                    //循环依赖源头，当两个类相互依赖但是都为完成初始化的时候，依赖注入进入死循环
                    createIOCInstance(beanDefinitionMap.get(value).getaClass(), value);
                }
                field.set(o, o1);
            }
            //属性填充 对value注解填充
        }
        mvc.put(beanName, o);
    }

    private void createIOCInstance(Class<?> aClass, String className) throws IllegalAccessException, InstantiationException {
        Object o = aClass.newInstance();
        Field[] fields = aClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            //依赖注入
            if (field.isAnnotationPresent(MyAutowired.class)) {
                String value = field.getAnnotation(MyAutowired.class).value();
                Object o1 = ioc.get(value);
                if (o1 == null) {
                    //循环依赖源头，当两个类相互依赖但是都为完成初始化的时候，依赖注入进入死循环
                    createIOCInstance(beanDefinitionMap.get(value).getaClass(), value);
                }
                field.set(0, o1);
            }
            //属性填充 对value注解填充
        }
        ioc.put(className, o);
    }

    private void initHandlerMapping() {
        if (mvc.isEmpty()) {
            return;
        }
        for (Object value : mvc.values()) {
            Class<?> aClass = value.getClass();
            String baseUrl = "";
            if (aClass.isAnnotationPresent(MyRequestMapping.class)) {
                baseUrl = aClass.getAnnotation(MyRequestMapping.class).value();
            }
            //遍历方法，使每个url与类和方法对应
            Method[] declaredMethods = aClass.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                if (!declaredMethod.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }
                String methodUrl = declaredMethod.getAnnotation(MyRequestMapping.class).value();
                String url = baseUrl + methodUrl;
                controllerMap.put(url, value);
                handlerMapping.put(url, declaredMethod);
                System.out.println(url + "==>" + declaredMethod);
            }
        }

    }
}